(function (angular) {
  'use strict';
  angular.module('fs.format').factory('formatService', ['$filter', 'storageService', 'SYS_PARAMS', function ($filter, storageService, SYS_PARAMS) {
    var defaultFormat = {
      number: '#0.0##############',
      money: '#,##0.00#',
      percentage: '#0.0#'
    };
    function getMaximumIntegerDigits(pattern) {
      if (!pattern)
        return 1;
      if (pattern.indexOf('#') == 0) {
        return 2147483647;
      }
      pattern.replace('[,]', '');
      return pattern.length;
    }
    function getMaximumFractionDigits(pattern) {
      if (!pattern)
        return 0;
      return pattern.length;
    }
    function round(number, decimal) {
      if (isNaN(number)) return '';
      if (decimal < 0)
        return new Big(number).toString();
      return new Big(number).round(decimal).toString();
    }

    function formatNumber(number, pattern) {
      var maximumIntegerDigits, maximumFractionDigits, formatInfo, formatOptions, numberArray, numberString;
      var formatArray, formatString, length, len, i, result = '', sign = '';
      if (number === null || angular.isUndefined(number) || (angular.isString(number) ? number.trim() == '' : false)) {
        return;
      }
      formatInfo = storageService.getFormatInfo() || {};
      pattern = formatInfo[pattern] || pattern;
      pattern = pattern || formatInfo.defaultFloat || defaultFormat.number;
      if (!/.*[#0]+.*/.test(pattern.replace(/[^#0,\.]*/, ''))) {
        return number;
      }

      formatOptions = {
        decimalSeparator: formatInfo.decimalSeparator || '.',
        groupSeparator: formatInfo.groupSeparator || ',',
        groupingUsed: pattern.indexOf(',') >= 0,
        groupingSize: ((pattern.indexOf('.') || pattern.length - 1) - pattern.indexOf(',') - 1)
      };

      formatArray = pattern.split('.');
      maximumIntegerDigits = getMaximumIntegerDigits(formatArray[0]);
      maximumFractionDigits = getMaximumFractionDigits(formatArray.length > 1 ? formatArray[1] : '');
      if (isNaN(number)) return '';
      number = round(number, maximumFractionDigits);
      numberString = '' + number;
      numberArray = numberString.split('.');
      if (numberArray[0].length > maximumIntegerDigits) {
        numberArray[0] = numberArray[0].substr(numberArray[0].length - maximumIntegerDigits);
        numberString = numberArray.join('.');
      }

      numberArray = numberString.split(".");
      numberString = numberArray[0];
      formatString = formatArray[0];
      len = numberArray[0].length - 1;
      for (i = formatString.length - 1; i >= 0; i--) {
        switch (formatString.substr(i, 1)) {
          case '#':
            if (len >= 0)
              result = numberString.substr(len--, 1) + result;
            break;
          case '0':
            if (len >= 0)
              result = numberString.substr(len--, 1) + result;
            else
              result = '0' + result;
            break;
          case ',':
            result = formatOptions.groupSeparator + result;
            break;
        }
      }
      if (len >= 0) {
        if (formatOptions.groupingUsed) {
          length = numberArray[0].length;
          for (; len >= 0; len--) {
            result = numberArray[0].substr(len, 1) + result;
            if (len > 0 && ((length - len) % formatOptions.groupingSize) == 0)
              result = formatOptions.groupSeparator + result;
          }
        } else
          result = numberString.substr(0, len + 1) + result;
      }
      result += formatOptions.decimalSeparator;
      numberString = numberArray.length > 1 ? numberArray[1] : '';
      formatString = formatArray.length > 1 ? formatArray[1] : '';
      len = 0;
      for (var i = 0; i < formatString.length; i++) {
        switch (formatString.substr(i, 1)) {
          case '#':
            if (len < numberString.length)
              result += numberString.substr(len++, 1);
            break;
          case '0':
            if (len < numberString.length)
              result += numberString.substr(len++, 1);
            else
              result += '0';
            break;
        }
      }
      if (result.charAt(0) == '-' || result.charAt(0) == '+') {
        sign = result.charAt(0);
        result = result.substring(1);
      }
      result = sign + result.replace(/^\,|^\./, '').replace(/\.$|\,$/, '');
      return result;
    }
    function formatMoney(money, pattern) {// pattern is ccy or ccy format
      // TODU: need to get ccyConfig from backend
      var formatInfo, siteInfo, ccyConfig = SYS_PARAMS.CCY_CONFIG, zeros = ".000000000000000000000000000000000000000000000000", fractionDigits;
      formatInfo = storageService.getFormatInfo() || {};
      siteInfo = storageService.getSiteInfo() || {};
      if (ccyConfig[angular.uppercase(pattern)] == undefined) {
        fractionDigits = ccyConfig[siteInfo.baseCcy];
      } else {
        fractionDigits = ccyConfig[angular.uppercase(pattern)];
      }
      if (!(money === null || angular.isUndefined(money) || (angular.isString(money) ? money.trim() == '' : false))) {
        pattern = (formatInfo.money0 || '#,##0') + zeros.substr(0, fractionDigits + 1);
      } else {
        pattern = formatInfo[pattern];
        pattern = pattern || formatInfo['defaultMoney'];
      }
      return formatNumber(money, pattern);
    }
    function formatSst(sensitiveInfo, pattern) {
      return $filter("sensitive")(sensitiveInfo, pattern.positiveOffset, pattern.negativeOffset, pattern.mask);
    }
    function formatPercentage(number, pattern) {
      var formatInfo = storageService.getFormatInfo() || {};
      pattern = formatInfo[pattern];
      pattern = pattern || defaultFormat.percentage;
      number = formatNumber(number * 100, pattern);
      return number = number ? number + '%' : number;
    }
    function formatDate(date, pattern) {
      var formatInfo = storageService.getFormatInfo() || {};
      pattern = formatInfo[pattern] || pattern;
      return $filter('dateFormat')(date, pattern);
    }
    function parseNumber(number) {
      var formatInfo, decimalSeparator, groupSeparator;
      if (angular.isNumber(number))
        return number;
      if (!angular.isString(number)) {
        return number;
      }
      if (number === null || angular.isUndefined(number) || (angular.isString(number) ? number.trim() == '' : false)) {
        return '';
      }
      formatInfo = storageService.getFormatInfo() || {};
      decimalSeparator = formatInfo.decimalSeparator || '.';
      groupSeparator = formatInfo.groupSeparator || ',';
      number = number.replace(eval("/\\" + groupSeparator + "/g"), "");
      number = number.replace(eval("/\\" + decimalSeparator + "/g"), ".");
      return isNaN(number) ? '' : new Big(number).toString();
    }
    function parseMoney(money) {
      return parseNumber(money);
    }
    function parsePercentage(percentage) {
      if (!percentage || percentage == '') {
        return percentage;
      }
      if (angular.isNumber(percentage))
        return percentage / 100;
      return parseNumber(percentage.replace(/%$/g, '')) / 100;
    }
    function parseDate(date, pattern) {
      var formatInfo, i_val = 0, i_format = 0, c = "", token = "", token2 = "", x, y, year = new Date().getFullYear();
      var month = 1, day = 1, hh = 0, mm = 0, ss = 0, sss = 0, ampm = "", zs = "", zh = 0, zm = 0;
      if (!date || !pattern)
        return date;
      formatInfo = storageService.getFormatInfo() || {};
      pattern = formatInfo[pattern] || pattern;
      // check the value is 0 - 9.
      this.isInteger = function (val) {
        for (var i = 0; i < val.length; i++) {
          if ("1234567890".indexOf(val.charAt(i)) == -1) {
            return false;
          }
        }
        return true;
      };
      // Gets the token from minlength to maxlength.
      this.getInt = function (str, i, minlength, maxlength) {
        for (var x = maxlength; x >= minlength; x--) {
          var token = str.substring(i, i + x);
          if (token.length < minlength) {
            return null;
          }
          if (this.isInteger(token)) {
            return token;
          }
        }
        return null;
      };

      this.buildDate = function (year, month, day, hh, mm, ss, sss, zh, zm, zs) {
        var date = new Date(0);
        var dateSetter = date.setFullYear;//zs != '' ? date.setUTCFullYear : date.setFullYear;
        var timeSetter = date.setHours;//zs != '' ? date.setUTCHours : date.setHours;
        var h = (hh || 0); //- zh;
        var m = (mm || 0); //- zm;
        var s = (ss || 0);
        var ms = Math.round(parseFloat('0.' + (sss || 0)) * 1000);
        dateSetter.call(date, year, month - 1, day);
        timeSetter.call(date, h, m, s, ms);
        return date;
      };

      while (i_format < pattern.length) {
        c = pattern.charAt(i_format);
        token = "";
        while ((pattern.charAt(i_format) == c) && (i_format < pattern.length)) {
          token += pattern.charAt(i_format++);
        }
        if (token == "yyyy" || token == "yy" || token == "y") {
          if (token == "yyyy") {
            x = 4;
            y = 4;
          }
          if (token == "yy") {
            x = 2;
            y = 2;
          }
          if (token == "y") {
            x = 2;
            y = 4;
          }
          year = this.getInt(date, i_val, x, y);
          if (year == null)
            return date;
          i_val += year.length;
        } else if (token == "MM" || token == "M") {
          month = this.getInt(date, i_val, token.length, 2);
          if (month == null || month < 1 || month > 12)
            return date;
          i_val += month.length;
        } else if (token == "dd" || token == "d") {
          day = this.getInt(date, i_val, token.length, 2);
          if (day == null || (day < 1) || (day > 31))
            return date;
          i_val += day.length;
        } else if (token == "hh" || token == "h") {
          hh = this.getInt(date, i_val, token.length, 2);
          if (hh == null || (hh < 1) || (hh > 12))
            return date;
          i_val += hh.length;
        } else if (token == "HH" || token == "H") {
          hh = this.getInt(date, i_val, token.length, 2);
          if (hh == null || (hh < 0) || (hh > 23))
            return date;
          i_val += hh.length;
        } else if (token == "mm" || token == "m") {
          mm = this.getInt(date, i_val, token.length, 2);
          if (mm == null || (mm < 0) || (mm > 59))
            return null;
          i_val += mm.length;
        } else if (token == "ss" || token == "s") {
          ss = this.getInt(date, i_val, token.length, 2);
          if (ss == null || (ss < 0) || (ss > 59))
            return date;
          i_val += ss.length;
        } else if (token == "sss" || token == "SSS" || token == "S") {
          sss = this.getInt(date, i_val, token.length, 3);
          if (sss == null || (sss < 0) || (sss > 999))
            return date;
          i_val += sss.length;
        } else if (token == "Z" || token == "z") {
          zs = date.substring(i_val, i_val + 1);
          zh = this.getInt(date, i_val + 1, 2, 2);
          zm = this.getInt(date, i_val + 3, 2, 2);
          zh = zs == '+' ? zh : -zh;
          zm = zs == '+' ? zm : -zm;
          i_val += 5;
        } else if (token == "a") {
          if (date.substring(i_val, i_val + 2).toLowerCase() == "am") {
            ampm = "AM";
          } else if (date.substring(i_val, i_val + 2).toLowerCase() == "pm") {
            ampm = "PM";
          } else {
            return date;
          }
          i_val += 2;
        } else {
          if (date.substring(i_val, i_val + token.length) != token) {
            return date;
          } else {
            i_val += token.length;
          }
        }
      }
      // validate data
      if (i_val != date.length)
        return date;
      // validate month
      if (month == 2) {
        if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
          if (day > 29)
            return date;
        } else {
          if (day > 28)
            return date;
        }
      }
      if ((month == 4) || (month == 6) || (month == 9) || (month == 11)) {
        if (day > 30)
          return date;
      }
      // set ampm
      if (hh < 12 && ampm == "PM") {
        hh = hh - 0 + 12;
      } else if (hh > 11 && ampm == "AM") {
        hh -= 12;
      }
      return this.buildDate(year, month, day, hh, mm, ss, sss, zh, zm, zs);
    }
    return {
      formatNumber: formatNumber,
      formatMoney: formatMoney,
      formatPercentage: formatPercentage,
      formatDate: formatDate,
      formatSst: formatSst,
      parseNumber: parseNumber,
      parseMoney: parseMoney,
      parsePercentage: parsePercentage,
      parseDate: parseDate,
      round: round
    };
  }]);
})(angular);
